home *** CD-ROM | disk | FTP | other *** search
/ Aminet 33 / Aminet 33 - October 1999.iso / Aminet / comm / net / TimeLord.lha / timelord / PROGRAMMING.NOTE next >
Encoding:
Text File  |  1999-07-31  |  10.2 KB  |  270 lines

  1. Programming NOTE:
  2.  
  3.   For those of you writing client programs for the internet. If you have
  4. noticed, when you are running a shell command that attaches to a host to
  5. get some information, you can abort a "stuck" request by simply typing
  6. <CTRL><c>. This is because the shell senses this sequence and sends a
  7. software interrupt to the process. Most, if not all, commands provided by
  8. current TCP stacks (AmiTCP, Termite TCP, ...) use a bsd standard which is
  9. that software interrupt signals will abort the current command.
  10.  
  11.   It would be nice if those of you programming GUI programs will add a way
  12. to simulate this so that commands, such as 'connect()', don't get "stuck"
  13. indefinately. What follows is a suggestion which works well. Feel free to
  14. use it, or a modification of it, in your code. I am providing this suggestion
  15. here because I am tired of running programs that sit and wait indefinately
  16. for some response, sometimes causing me to forceibly hang up the connection
  17. and reboot just to terminate the command.
  18.  
  19. This "pseudo-code" is intended mostly as information. There is plenty of
  20. room for variation. However, specifically in regard to 'bsd' function calls,
  21. this technique can be used to add a timeout ability to any 'bsd' functions
  22. which do not have a timeout built in, such as 'connect()'. You could also
  23. use it to enhance the timeout for any function where the supplied timeout
  24. does not work quite as expected or desired.
  25.  
  26. So, without further adieu, here it is...
  27.  
  28.   #include <signal.h>
  29.   #include <dos.h>
  30.   #include <dos/dos.h>
  31.   #include <proto/exec.h>
  32.  
  33.   int Signals = 0;
  34.   struct MsgPort *TimeOutPort = NULL;
  35.  
  36.   /* handler for trapping SIGINT
  37.    * NOTE: interrupt handlers should be kept
  38.    * as simple as possible!! YOU HAVE BEEN WARNED!!!
  39.    */
  40.   int SigInt( int Sigs ) {
  41.     Signals |= Sigs;
  42.     signal( SIGINT, SigInt );
  43.   }
  44.  
  45.   /* Handler for trapping software exceptions.
  46.    * IMPORTANT: This MUST be defined using this calling convention!!
  47.    * This is because this is how the OS calls it when the exception happens.
  48.    * (NOTE: declaring it 'static' is optional however)
  49.    */
  50.   __aligned __regargs static ULONG ExceptHand( ULONG Sigs, ULONG LocalData ) {
  51.     struct Task    *pTask = NULL;
  52.  
  53.     /* disable exception(s) specified in 'Sigs' so
  54.      * they won't trigger additional exceptions while
  55.      * you are processing this one.
  56.      */
  57.     SetExcept( 0, Sigs );
  58.  
  59.     /* signal a SIGINT to the task to abort the
  60.      * socket command (i.e. 'connect()') or other process
  61.      * NOTE: DO NOT use the standard 'C' function 'raise()'
  62.      * to trigger the SIGINT as this causes the SIGINT to be
  63.      * processed NOW rather than when you return from this exception
  64.      * handler. As a result, by the time you return from this handler,
  65.      * the signal has already been processed, and the signal
  66.      * reset. As a result, the command/process you are trying to
  67.      * interrupt never sees the signal and is never interrupted!!!
  68.      * Since 'C' does not check for a SIGINT until you are performing
  69.      * I/O, and since bsd function calls are I/O commands, then by
  70.      * using the exec function 'Signal()' to set the SIGINT, then it
  71.      * will check upon resuming the bsd function call which was
  72.      * interrupted, thus aborting the command.
  73.      */
  74.     pTask = FindTask( NULL ); /* find "my" task info */
  75.     if ( pTask ) {
  76.         /* set the following signals:
  77.      * SIGBREAKF_CTRL_C      SIGINT
  78.      * Sigs                  re-set the signal that triggered the exception
  79.      * signal 8              it occasionally froze unless this was set
  80.      */
  81.     Signal( pTask, SIGBREAKF_CTRL_C | Sigs | 1<<8 ); /* signal me */
  82.     }
  83.     return Sigs;
  84.   }
  85.  
  86.   /* set a timeout with an exception */
  87.   void open_timer( int TimeOutSecs ) {
  88.     if ( !TimeOutPort ) {
  89.       /* open port to timer, and open timer device... */
  90.       TimeOutPort = OpenPort( ... );
  91.        :
  92.       OpenDevice( ... );
  93.        :
  94.        :
  95.       /* use the following to establish that messages
  96.        * received at port will cause exceptions.
  97.        */
  98.       if ( TimeOutPort ) {
  99.         SetExcept( 1 << TimeOutPort->mp_SigBit, 1 << TimeOutPort->mp_SigBit );
  100.       }
  101.       /* now, send a timeout request of 'TimeOutSecs'
  102.        * seconds to the timer device.
  103.        */
  104.     }
  105.   }
  106.  
  107.   /* disable port's exception and close timer and port */
  108.   void close_timer( void ) {
  109.     if ( TimeOutPort ) {
  110.       /* disable exception so any pending messages
  111.        * won't generate exceptions
  112.        */
  113.       SetExcept( 0, 1 << TimeOutPort->mp_SigBit );
  114.        :
  115.        :
  116.       /* perform normal cleanup, such as aborting any
  117.        * remaining pending timeout events and empty
  118.        * the port. then close the timer device and the port.
  119.        */
  120.       CloseDevice( ... );
  121.        :
  122.       ClosePort( ... );
  123.     }
  124.     TimeOutPort = NULL;
  125.     if ( Signals & SIGINT ) {
  126.       /* any signal code to be executed by the SIGINT
  127.        * (could be additional cleanup,...)
  128.        */
  129.       :
  130.       :
  131.       Signals &= ~SIGINT;
  132.     }
  133.   }
  134.  
  135.   your_main_loop( void ) {
  136.     open_timer( 10 ); /* set a 10 second timeout */
  137.     connect(); /* bsd socket function which we are trying to timeout */
  138.     close_timer(); /* abort timeout if connect succeeded, or cleanup */
  139.  
  140.     for( .... ) {
  141.       :
  142.       :
  143.       open_timer( 10 ); /* set a 10 second timeout */
  144.       another_bsd_function_needing_timeout();
  145.       close_timer(); /* abort timeout if connect succeeded, or cleanup */
  146.       :
  147.       :
  148.       open_timer( 10 ); /* set a 10 second timeout */
  149.       another_bsd_function_needing_timeout();
  150.       close_timer(); /* abort timeout if connect succeeded, or cleanup */
  151.       :
  152.       :
  153.     }
  154.   }
  155.  
  156.   void open_socket( void ) {
  157.     /* build host address, get access protocol, and
  158.      * open the socket
  159.      */
  160.     socket( ... );
  161.   }
  162.  
  163.   void close_socket( void ) {
  164.     /* close the socket */
  165.     ShutDown( ... );
  166.     CloseSocket( ... );
  167.   }
  168.  
  169.   int main( void ) {
  170.     struct Task *pTask = FindTask( NULL );
  171.     :
  172.     :
  173.     /* establish pointer to exception handler */
  174.     if ( pTask ) {
  175.        pTask->tc_ExceptCode = &ExceptHand;
  176.        pTask->tc_ExceptData = NULL;
  177.     }
  178.     /* establish pointer to SIGINT handler
  179.      * DO NOT use SIG_IGN or SIG_DFL instead of
  180.      * 'SigInt'. using SIG_IGN will ignore signal
  181.      * which nullifies all work being done. SIG_DFL
  182.      * establishes default response to a SIGINT which
  183.      * is to abort current process and exit from the
  184.      * program, possibly leaving socket open and other
  185.      * resources still allocated!!!
  186.      */
  187.     signal( SIGINT, SigInt );
  188.     :
  189.     :
  190.     open_socket();
  191.  
  192.     /* process */
  193.     your_main_loop();
  194.  
  195.     close_socket();
  196.  
  197.     return 0;
  198.   }
  199.  
  200. Explanation:
  201.  
  202.   This (pseudo) code:
  203.   1) establishes a timeout to wrap around any command needing one
  204.   2) sets a task exception to be generated when the timeout message arrives
  205.      at the timer's message port
  206.   3) sets up an exception handler which is called when the exception is
  207.      triggered. this handler, in turn, triggers a normal software interrupt
  208.      SIGINT signal. upon return from the exception handler, the SIGINT
  209.      handling is executed, including calling your handler and also
  210.      aborting the current I/O command (i.e. connect())
  211.   4) establishes a handler to handle the SIGINT when triggered so your
  212.      program does not abort and exit when it comes in
  213.  
  214. Since the standard for 'bsd' function calls is to abort when a software
  215. interrupt is received by the program, then this should work for pretty
  216. much all 'bsd' socket calls, as well as for any functions which perform some
  217. kind of I/O (i.e. fprintf()).
  218.  
  219. While it is true that message ports can be set to cause a soft interrupt
  220. when messages arrive, it looks very trickey to get this to work correctly.
  221. From the information I have (which is very scarce), it looks like you have
  222. to set up a signal handler, written in assembler, and installed in the OS
  223. signal handler list, which seems needlessly complicated for what I was
  224. trying to do. This method is very simple, in comparison, and requires no
  225. assembler code to get it to work, which makes it much easier to maintain.
  226.  
  227. This method can also be tailored to a number of other situations. For example,
  228. if you find that you need to continually check for the arrival of messages
  229. during a processing loop, this method can be used to elimanate this need.
  230. Let the operating system "tell" you when the message is there by triggering
  231. an exception. This allows you to spend full concentration on whatever you
  232. are needing to do in your loop, and defers the message checking to the OS!!
  233. If desired, you could add code to the exception handler or your SIGINT handler
  234. to terminate the loop by setting the correct condition to terminate the
  235. processing loop eliminating the need to check for a SIGINT in the loop as well.
  236.  
  237. --------------------
  238.  
  239. A word on Exceptions:
  240.  
  241. When you set an exception to be triggered upon receipt of a particular
  242. task signal, the corresponding task signal is reset to prevent the exception
  243. from being tripped again. As a result, if you call 'Wait()' or 'WaitPort()'
  244. after the exception has been triggered, and more messages exist at the port,
  245. the 'Wait' commands will NOT immediately return because the signal bit
  246. has been reset by the exception. You have 2 options at this point:
  247.  
  248. 1) use Signal() to set the signal bit again so that the next 'Wait' call
  249.    will not block.
  250.  
  251. 2) process all remaining messages at the port before moving on. Do this by
  252.    using 'GetMsg()/ReplyMsg()' until 'GetMsg()' returns NULL.
  253.  
  254. Option 1 should ONLY be used while the port's exception is disabled.
  255. Otherwise, setting the signal bit will trigger the exception again reseting
  256. the bit again (nice huh??) and trigger another SIGINT.
  257.  
  258. Option 2 should be used whenever possible. Then when all messages have been
  259. processed, it is safe to re-enable the exception for the port again and let
  260. message arrivals set the signal bit.
  261.  
  262. Another WARNING: try not to use other kinds of I/O commands within the
  263. exception handler. If you do, use them BEFORE calling 'Signal()' to set the
  264. signal. The reason is because, as stated earlier, checks for SIGINT are made
  265. during I/O calls. 'printf()' and commands like that are I/O commands because
  266. they "output" to the screen. The check for, and processing of, the SIGINT will
  267. most likely occur DURING this call, which means, by the time you return
  268. from your exception handler, the SIGINT has already been processed, thus
  269. putting you back where you started... stuck on some command.
  270.